//----------------------------------------------------------------------
// sgMotion Animation Library
//----------------------------------------------------------------------
//
// SunBurn open source animation library created and maintained
// by the SunBurn community.
//
// Based on the excellent (though abandoned) XNAnimation library, this
// project picks up development and provides full integration with both
// the SunBurn Engine and SunBurn Framework.
//
// Original XNAnimation library:
//
// http://www.codeplex.com/xnanimation
//
// A big thanks to Tom Looman for talking over this project, and Bruno
// Evangelista for his original work.
//
// Thanks guys!
//
//----------------------------------------------------------------------

#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SgMotion;
using SgMotion.Controllers;
using SgMotion.Effects;

// Synapse Gaming namespaces
using SynapseGaming.LightingSystem.Core;
using SynapseGaming.LightingSystem.Editor;
using SynapseGaming.LightingSystem.Effects;
using SynapseGaming.LightingSystem.Lights;
using SynapseGaming.LightingSystem.Rendering;
using SynapseGaming.LightingSystem.Rendering.Forward;
using System.IO;
#endregion

//----------------------------------------------------------------------
//---- Modified XNAnimation Library (based on: 0.7.0.0 BETA 2) ---------
//---- Original Creator: Bruno Evangelista -----------------------------
//---- Modified by: Tom Looman -----------------------------------------
//---- Changes: Sunburn Integration (for version 1.3.1) ----------------
//---- Updated sample & library code for Sunburn 1.3.1 (And XNA GS 3.1)-
//---- Link to original library: http://www.codeplex.com/xnanimation ---
//---- Link to my Blog: http://coreenginedev.blogspot.com --------------
//----------------------------------------------------------------------

namespace SgMotionSample
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class AnimationSample : Game
    {
        private GraphicsDeviceManager graphics;
        
        // The SunBurn lighting system.
        LightingSystemManager lightingSystemManager;
        SceneInterface sceneInterface;
        LightingSystemEditor editor;
        SceneState sceneState;
        SceneEnvironment environment;
        LightingSystemPreferences preferences;
        SplashScreenGameComponent splashScreenGameComponent;

        // Text UI
        private SpriteBatch spriteBatch;
        private SpriteFont spriteFont;
        private SpriteFont spriteFontBig;

        // Input
        private KeyboardState keyboardState;
        private KeyboardState lastKeyboardState;

        private string message = "XNA Animation System";

        // SKINNED MODEL STUFF
        #region SkinnedModel stuff

        private int activeAnimationClip;

        private SkinnedModel skinnedModel;

        private AnimationController animationController;
        private float boneRotation = 0;

        #endregion

        private Matrix cameraView;
        private Matrix cameraProjection;

        const float moveScale = 100.0f;
        const string userPreferencesFile = "UserPreferences.xml";

        // SceneObject for our Marine model
        SceneObject marineObject;


        #region Constructor
        public AnimationSample()
        {
            graphics = new GraphicsDeviceManager(this);

            #if XBOX360
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            #else
            graphics.PreferredBackBufferWidth = 1024;
            graphics.PreferredBackBufferHeight = 576;
            #endif
            graphics.PreferMultiSampling = true;

            // Minimum requirements for dynamic shadows (if disabling shadows these can be dropped to PS_2_0/VS_2_0).
            graphics.MinimumPixelShaderProfile = ShaderProfile.PS_3_0;
            graphics.MinimumVertexShaderProfile = ShaderProfile.VS_3_0;
            graphics.PreparingDeviceSettings += PrepareDeviceSettings;

            // Used for advanced edge cleanup.
            graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;

            // Required for lighting system.
            splashScreenGameComponent = new SplashScreenGameComponent(this, graphics);
            Components.Add(splashScreenGameComponent);

            // Create the lighting system.
            lightingSystemManager = new LightingSystemManager(Services);
            sceneState = new SceneState();

            // Create the scene interface. Acts as a service provider containing all scene managers
            // and returning them by type (including custom managers). Also acts as a component
            // container where calls to manager methods on the SceneInterface (such as BeginFrameRendering,
            // Unload, ...) are automatically called on all contained managers.
            //
            // This design allows managers to be plugged-in like modular components and for managers
            // to easily be added, removed, or replaced with custom implementations.
            //
            sceneInterface = new SceneInterface(graphics);
            sceneInterface.CreateDefaultManagers(false, false, false);

            // Create the editor and add it to the scene interface (this is not included
            // in the default manager set).
            editor = new LightingSystemEditor(Services, graphics, this);
            sceneInterface.AddManager(editor);

            // Set the in-editor camera speed.
            editor.MoveScale = moveScale;

            // Load the user preferences (example - not required).
            preferences = new LightingSystemPreferences();
#if !XBOX
            if (File.Exists(userPreferencesFile))
                preferences.LoadFromFile(userPreferencesFile);
            else
#endif
            {
                preferences.EffectDetail = DetailPreference.High;
                preferences.MaxAnisotropy = 4;
                preferences.PostProcessingDetail = DetailPreference.High;
                preferences.ShadowDetail = DetailPreference.High;
                preferences.ShadowQuality = 1.0f;
                preferences.TextureQuality = DetailPreference.High;
                preferences.TextureSampling = SamplingPreference.Anisotropic;
            }


            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Improves overall performance with dynamic shadows.
        /// </summary>
        private void PrepareDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            e.GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage = RenderTargetUsage.PlatformContents;
        }

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }
        #endregion

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("Fonts/Arial");
            spriteFontBig = Content.Load<SpriteFont>("Fonts/Calibri");

            // Load the skinned model
            skinnedModel = Content.Load<SkinnedModel>("Models/PlayerMarine");

            // Create an animation controller and start a clip
            animationController = new AnimationController(skinnedModel.SkeletonBones);
            animationController.StartClip(skinnedModel.AnimationClips["Idle"]);

            // Setup camera
            cameraView = Matrix.CreateLookAt(new Vector3(0, 15, 30), new Vector3(0, 10, 0), Vector3.Up);
            cameraProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)graphics.GraphicsDevice.Viewport.Width
                / (float)graphics.GraphicsDevice.Viewport.Height, 1, 1000);

            // Likewise add static lights to the LightManager.
            // LightRigs contain many lights and light groups.
            LightRig rig = Content.Load<LightRig>("Lights/light_rig");
            sceneInterface.LightManager.Submit(rig);

            // Load the scene settings.
            environment = Content.Load<SceneEnvironment>("scene_environment");

            // Apply the user preferences (example - not required).
            sceneInterface.ApplyPreferences(preferences);

            // SceneObject setup
            marineObject = new SceneObject(skinnedModel.Model);
            marineObject.ObjectType = ObjectType.Dynamic;
            marineObject.Visibility = ObjectVisibility.RenderedAndCastShadows;
            marineObject.World = Matrix.Identity;

            // Submit the SceneObject so it can be rendered
            sceneInterface.ObjectManager.Submit(marineObject);
        }

        #region Unload
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here

            sceneInterface.Unload();
            lightingSystemManager.Unload();
        }
        #endregion

        protected override void Update(GameTime gameTime)
        {
            // Allows game controls when not editing the scene.
            if (!editor.EditorAttached)
            {
                #region Input
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                    Keyboard.GetState().IsKeyDown(Keys.Escape))
                    this.Exit();

                lastKeyboardState = keyboardState;
                keyboardState = Keyboard.GetState();

                if (keyboardState.IsKeyDown(Keys.Escape))
                    Exit();

                if (keyboardState.IsKeyDown(Keys.Left) &&
                    lastKeyboardState.IsKeyUp(Keys.Left))
                {
                    activeAnimationClip = (activeAnimationClip - 1);
                    if (activeAnimationClip < 0)
                        activeAnimationClip = skinnedModel.AnimationClips.Count - 1;

                    // Change the current animation clip
                    animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClip],
                        TimeSpan.FromSeconds(0.3f));
                }
                else if (keyboardState.IsKeyDown(Keys.Right) &&
                    lastKeyboardState.IsKeyUp(Keys.Right))
                {
                    activeAnimationClip = (activeAnimationClip + 1) % skinnedModel.AnimationClips.Count;

                    // Change the current animation clip
                    animationController.CrossFade(
                        skinnedModel.AnimationClips.Values[activeAnimationClip],
                        TimeSpan.FromSeconds(0.3f));
                }

                if (keyboardState.IsKeyDown(Keys.PageUp))
                {
                    message = "Custom Per-Bone Transformation";

                    boneRotation += 0.02f;
                }
                else if (keyboardState.IsKeyDown(Keys.PageDown))
                {
                    message = "Custom Per-Bone Transformation";

                    boneRotation -= 0.02f;
                }

                if (keyboardState.IsKeyDown(Keys.F1))
                {
                    message = "No Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.None;
                    animationController.OrientationInterpolation = InterpolationMode.None;
                    animationController.ScaleInterpolation = InterpolationMode.None;
                }
                else if (keyboardState.IsKeyDown(Keys.F2))
                {
                    message = "Linear Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.Linear;
                    animationController.OrientationInterpolation = InterpolationMode.Linear;
                    animationController.ScaleInterpolation = InterpolationMode.Linear;
                }
                else if (keyboardState.IsKeyDown(Keys.F3))
                {
                    message = "Cubic Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.Cubic;
                    animationController.OrientationInterpolation = InterpolationMode.Linear;
                    animationController.ScaleInterpolation = InterpolationMode.Cubic;
                }
                else if (keyboardState.IsKeyDown(Keys.F4))
                {
                    message = "Spherical Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.Linear;
                    animationController.OrientationInterpolation = InterpolationMode.Spherical;
                    animationController.ScaleInterpolation = InterpolationMode.Linear;
                }


                if (keyboardState.IsKeyDown(Keys.D5) && lastKeyboardState.IsKeyUp(Keys.D5))
                {
                    message = "Animation Looping Control";
                    animationController.LoopEnabled = !animationController.LoopEnabled;
                }

                if (keyboardState.IsKeyDown(Keys.Tab) && lastKeyboardState.IsKeyUp(Keys.Tab))
                {
                    message = "Forward and Backward Playback";

                    if (animationController.PlaybackMode == PlaybackMode.Forward)
                        animationController.PlaybackMode = PlaybackMode.Backward;
                    else
                        animationController.PlaybackMode = PlaybackMode.Forward;
                }

                if (keyboardState.IsKeyDown(Keys.Up))
                {
                    message = "Animation Speed Control";
                    animationController.Speed += 0.005f;
                }
                else if (keyboardState.IsKeyDown(Keys.Down))
                {
                    message = "Animation Speed Control";
                    animationController.Speed -= 0.005f;
                    if (animationController.Speed < 0.1f)
                        animationController.Speed = 0.1f;
                }
                #endregion
            }

            // Rotate the marine
            marineObject.World *= Matrix.CreateFromAxisAngle(Vector3.Up, 0.02f);

            // Update the bone transforms
            marineObject.SkinBones = animationController.SkinnedBoneTransforms;

            // Keep the sceneInterface updated
            sceneInterface.Update(gameTime);

            // Update the animation controller
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            // Display Splashscreen on start
            if (!SplashScreenGameComponent.DisplayComplete)
            {
                base.Draw(gameTime);
                return;
            }

            // Render the scene.
            sceneState.BeginFrameRendering(cameraView, cameraProjection, gameTime, environment);
            sceneInterface.BeginFrameRendering(sceneState);

            // Add custom rendering that should occur before the scene is rendered.

            sceneInterface.RenderManager.Render();

            // Add custom rendering that should occur after the scene is rendered.
            Draw2D();

            sceneInterface.EndFrameRendering();
            sceneState.EndFrameRendering();

            base.Draw(gameTime);
        }

        #region Draw2D
        private void Draw2D()
        {
            spriteBatch.Begin();

            spriteBatch.DrawString(spriteFont,
                "Model: " + "Marine" + "    Animation: " +
                    skinnedModel.AnimationClips.Keys[activeAnimationClip], new Vector2(10, 10),
                Color.Blue);

            spriteBatch.DrawString(spriteFont, "Speed: ", new Vector2(10, 40), Color.White);
            spriteBatch.DrawString(spriteFont, "Looping: ", new Vector2(10, 60), Color.White);
            spriteBatch.DrawString(spriteFont, "Blending: ", new Vector2(10, 90), Color.White);
            spriteBatch.DrawString(spriteFont, "Playing: ", new Vector2(10, 130), Color.White);

            spriteBatch.DrawString(spriteFont, "" + (int) (animationController.Speed * 1000) * 0.001f,
                new Vector2(140, 40), Color.Red);
            spriteBatch.DrawString(spriteFont, "" + animationController.LoopEnabled, new Vector2(140, 60),
                Color.Red);
            spriteBatch.DrawString(spriteFont, "None", new Vector2(140, 90), Color.Red);
            spriteBatch.DrawString(spriteFont, "" + animationController.PlaybackMode,
                new Vector2(140, 130), Color.Red);

            spriteBatch.DrawString(spriteFontBig, message, new Vector2(20, 715), Color.Yellow);
            spriteBatch.End();
        }
        #endregion
    }
}